#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/io.h>
#include <linux/err.h>

#define NEWCHRLED_NAME  "newchrled"

/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO5_IO03_BASE (0X020E0028)
#define SW_PAD_GPIO5_IO03_BASE (0X020E02B4)
#define GPIO5_DR_BASE (0X020AC000)
#define GPIO5_GDIR_BASE (0X020AC004)

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO5_IO03;
static void __iomem *SW_PAD_GPIO5_IO03;
static void __iomem *GPIO5_DR;
static void __iomem *GPIO5_GDIR;

/**
 * LED 设备结构体
 */
struct newchrled_dev{
    dev_t devid;  /* 设备号 */
    int major;  /* 主设备号 */
    int minor;  /* 次设备号 */
    struct cdev cdev;  /* cdev */
    struct class *class;  /* 类 */
    struct device *device;  /* 设备 */
};

struct newchrled_dev newchrled;  /* 声明一个newchrled设备 */

int led_open (struct inode *node, struct file *filep)
{
    filep->private_data = &newchrled;
    return 0;
}

ssize_t led_write (struct file *filep, const char __user *buf, size_t size, loff_t *offset)
{
    int ret;
    u32 val;
    unsigned char databuf[1];

    ret = __copy_from_user(databuf, buf, 1);
    if(ret < 0)
    {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    if(databuf[0] != 0)  //打开 led
    {
        val = readl(GPIO5_DR);
        val &= (0 << 3);
        writel(val, GPIO5_DR);
    }
    else  //关闭 led
    {
        val = readl(GPIO5_DR);
        val |= (1 << 3);
        writel(val, GPIO5_DR);
    }

    return size;
}

int led_release (struct inode *node, struct file *filep)
{
    struct newchrled_dev *dev = (struct newchrled_dev*)filep->private_data;
    printk("release newchrled.major = %d, newchrled.minor = %d\r\n", dev->major, dev->minor);
    filep->private_data = NULL;

    return 0;
}

static const struct file_operations led_operations = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
    .release = led_release
};

static int __init led_init(void)
{
    int ret;
    u32 val = 0;

    /* 初始化 LED 灯 */
    // 1、寄存器地址映射
    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    SW_MUX_GPIO5_IO03 = ioremap(SW_MUX_GPIO5_IO03_BASE, 4);
    SW_PAD_GPIO5_IO03 = ioremap(SW_PAD_GPIO5_IO03_BASE, 4);
    GPIO5_DR = ioremap(GPIO5_DR_BASE, 4);
    GPIO5_GDIR = ioremap(GPIO5_GDIR_BASE, 4);

    /* 2、使能GPIO5时钟 */
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 30);/* 清除以前的设置 */
    val |= (3 << 30);/* 设置新值 */
    writel(val, IMX6U_CCM_CCGR1);

    /* 3、设置GPIO5_IO03的复用功能，将其复用为GPIO5_IO03。*/
   writel(5, SW_MUX_GPIO5_IO03);
   /* 寄存器SW_PAD_GPIO5_IO03设置IO属性 */
   writel(0x10B0, SW_PAD_GPIO5_IO03);

   /* 4、设置GPIO5_IO03为输出功能 */
   val = readl(GPIO5_GDIR);
   val &= ~(1 << 3); /* 清除以前的设置 */
   val |= (1 << 3); /* 设置为输出 */
   writel(val, GPIO5_GDIR);
   
   /* 5、默认打开LED */
   val = readl(GPIO5_DR);
   val &= (0 << 3);
   writel(val, GPIO5_DR);

    printk("led init \r\n");
    /* 创建设备号 */
    if(newchrled.major){
       newchrled.devid = MKDEV(newchrled.major, 0);
       ret = register_chrdev_region(newchrled.devid, 1, NEWCHRLED_NAME);
    }
    else{
        ret = alloc_chrdev_region(&newchrled.devid, 0, 1, NEWCHRLED_NAME);
        newchrled.major = MAJOR(newchrled.devid);  // 提取主设备号
        newchrled.minor = MINOR(newchrled.minor);  // 提取次设备号
    }
    if(ret < 0)
    {
        printk("newchrled chrdev region error\r\n");
        goto fail_devid;
    }
    printk("newchrled.major = %d, newchrled.minor = %d\r\n", newchrled.major, newchrled.minor);

    /* 初始化设备号 */
    newchrled.cdev.owner = THIS_MODULE;
    cdev_init(&newchrled.cdev, &led_operations);
    /* 添加 cdev */
    ret = cdev_add(&newchrled.cdev, newchrled.devid, 1);
    if(ret < 0)
    {
        printk("newchrled cdev add error\r\n");
        goto fail_cdev;
    }
    /* 创建类 */
    newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
    if(IS_ERR(newchrled.class)){
        ret = PTR_ERR(newchrled.class);
        goto fail_class;
    }

    /* 创建设备 */
    newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
    if(IS_ERR(newchrled.device)){
        ret = PTR_ERR(newchrled.device);
        goto fail_device;
    }

    return 0;

fail_device:
    class_destroy(newchrled.class);
fail_class:
    cdev_del(&newchrled.cdev);
fail_cdev:
    unregister_chrdev_region(newchrled.devid, 1);
fail_devid:
    return ret;
}

static void __exit led_exit(void)
{
    u32 val;
    /* 关闭LED */
    val = readl(GPIO5_DR);
    val |= (1 << 3);
    writel(val, GPIO5_DR);

    // 取消地址映射
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO5_IO03);
    iounmap(SW_PAD_GPIO5_IO03);
    iounmap(GPIO5_DR);
    iounmap(GPIO5_GDIR);

    printk("led exit \r\n");
    /* 删除设备 */
    device_destroy(newchrled.class, newchrled.devid);
    /* 删除类 */
    class_destroy(newchrled.class);
    /* 删除设备号 */
    cdev_del(&newchrled.cdev);
    /* 注销字符设备驱动 */
    unregister_chrdev_region(newchrled.devid, 1);
}

/**
 * 模块入口和出口
 */
module_init(led_init);  // 入口
module_exit(led_exit);  // 出口

/**
 * LICENSE 和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("george < g_yubo@163.com >");
